const { ccclass, property } = cc._decorator;

@ccclass("item_template")
export class item_template {
    @property(cc.String)
    key: string = "";
    @property(cc.Node)
    node: cc.Node = null;
}

export type ScrollItemData = {
    index?: number;
    key: string;
    itemData: any;
}

enum ScrollDirection {
    Vertical = 1,
    Horizontal = 2,
}

type ScrollViewParams = {
    cb_host?: any; //回调函数host
    item_setter: (item: cc.Node, data: any) => [number, number]; //item更新setter
    recycle_cb?: (item: cc.Node, key: string) => void; //回收时的回调
    scroll_to_end_cb?: () => void; //滚动到尽头的回调
}

type ScrollItem = {
    x: number;
    y: number;
    width: number;
    height: number;
    data: ScrollItemData;
    node: cc.Node;
}

@ccclass
export class ScrollViewCom extends cc.Component {

    @property([item_template])
    item_prefabs: item_template[] = [];
    @property()
    dir: number = ScrollDirection.Vertical;
    @property()
    gap_x: number = 0;
    @property()
    gap_y: number = 0;
    @property()
    auto_scrolling: boolean = false;
    @property()
    updateInterval: number = 0.2;

    private scrollview: cc.ScrollView;
    private content: cc.Node;
    private width: number = 0;
    private height: number = 0;

    private item_templates: Map<string, cc.Node>;
    private node_pools: Map<string, cc.NodePool>;
    private cb_host: any;
    private item_setter: (item: cc.Node, data: any) => [number, number];
    private recycle_cb: (item: cc.Node, key: string) => void;
    private scroll_to_end_cb: () => void;
    private items: ScrollItem[];
    private start_index: number;
    private stop_index: number;
    private updateTimer: number = 0;
    private curPos: number = -1;

    init(params: ScrollViewParams) {
        this.node_pools = new Map();
        this.item_templates = new Map();
        this.item_prefabs.forEach((tpl) => {
            tpl.node.active = false;
            this.item_templates.set(tpl.key, tpl.node);
        });

        this.width = this.node.width;
        this.height = this.node.height;
        this.cb_host = params.cb_host;
        this.item_setter = params.item_setter;
        this.recycle_cb = params.recycle_cb;
        this.scroll_to_end_cb = params.scroll_to_end_cb;

        this.scrollview = this.node.getComponent(cc.ScrollView);
        this.content = this.scrollview.content;
        this.content.width = this.dir == ScrollDirection.Vertical ? this.width : this.height;
        this.scrollview.node.setContentSize(this.width, this.height);
        this.scrollview.vertical = this.dir == ScrollDirection.Vertical;
        this.scrollview.horizontal = this.dir == ScrollDirection.Horizontal;
        this.scrollview.inertia = true;
        this.scrollview.node.on("scroll-to-bottom", this.on_scroll_to_end, this);
        this.scrollview.node.on("scroll-to-right", this.on_scroll_to_end, this);
    }

    private on_scroll_to_end() {
        if (this.scroll_to_end_cb) {
            this.scroll_to_end_cb.call(this.cb_host);
        }
    }

    //获取当前显示范围
    private getRange() {
        let start: number = 0;
        let stop: number = 0;
        if (this.dir == ScrollDirection.Vertical) {
            let posy: number = this.content.y;
            if (posy < 0)
                posy = 0;
            if (posy > this.content.height - this.height)
                posy = this.content.height - this.height;

            let viewport_start: number = -posy;
            let viewport_stop: number = viewport_start - this.height;

            while (this.items[start].y - this.items[start].height > viewport_start)
                start++;
            stop = start;
            while (this.items[stop] && this.items[stop].y > viewport_stop)
                stop++;
        }
        else {
            let posx: number = this.content.x;
            if (posx > 0)
                posx = 0;
            if (posx < this.width - this.content.width)
                posx = this.width - this.content.width;

            let viewport_start: number = -posx;
            let viewport_stop: number = viewport_start + this.width;

            while (this.items[start].x + this.items[start].width < viewport_start)
                start++;
            stop = start;
            while (this.items[stop] && this.items[stop].x < viewport_stop)
                stop++;
        }
        return [start, stop];
    }

    update(dt) {
        this.updateTimer += dt;
        if (this.updateTimer < this.updateInterval) return;
        if (!this.items || !this.items.length) return;
        var pos = this.dir == ScrollDirection.Vertical ? this.content.y : this.content.x;
        if (this.curPos == pos) return;
        this.curPos = pos;
        var [start, stop] = this.getRange();
        if (start == this.start_index && stop == this.stop_index) return;
        this.start_index = start;
        this.stop_index = stop;
        this.render_items();
    }

    private spawn_node(key: string): cc.Node {
        let node: cc.Node;
        let pools: cc.NodePool = this.node_pools.get(key);
        if (pools && pools.size() > 0) {
            node = pools.get();
        }
        else {
            node = cc.instantiate(this.item_templates.get(key));
            node.active = true;
        }
        node.parent = this.content;
        return node;
    }

    private recycle_item(item: ScrollItem) {
        if (item.node && cc.isValid(item.node)) {
            let pools: cc.NodePool = this.node_pools.get(item.data.key);
            if (!pools) {
                pools = new cc.NodePool();
                this.node_pools.set(item.data.key, pools);
            }
            item.node.cleanup();
            pools.put(item.node);
            if (this.recycle_cb) {
                this.recycle_cb.call(this.cb_host, item.node, item.data.key);
            }
            item.node = null;
        }
    }

    clear_items() {
        if (this.items) {
            this.items.forEach((item) => {
                this.recycle_item(item);
            });
        }
    }

    private render_items() {
        let item: ScrollItem;
        for (var i: number = 0, len: number = this.items.length; i < len; i++) {
            item = this.items[i];
            if (!item) return;

            if (i < this.start_index || i > this.stop_index) {
                if (item.node) {
                    this.recycle_item(item);
                }
            }
            else {
                if (!item.node) {
                    item.node = this.spawn_node(item.data.key);
                    this.item_setter.call(this.cb_host, item.node, item.data);
                }
                item.node.setPosition(item.x, item.y);
            }
        }
    }

    private pack_item(index: number, data: ScrollItemData): ScrollItem {
        let _templates = this.item_templates.get(data.key)
        let item: ScrollItem = {
            x: 0, y: 0,
            width: _templates.width,
            height: _templates.height,
            data: data,
            node: null
        };
        return item;
    }

    private layout_items(start: number) {
        if (this.items.length <= 0) {
            return;
        }
        let start_pos: number = 0;
        if (start > 0) {
            let prev_item: ScrollItem = this.items[start - 1];
            if (this.dir == ScrollDirection.Vertical) {
                start_pos = prev_item.y - prev_item.height - this.gap_y;
            }
            else {
                start_pos = prev_item.x + prev_item.width + this.gap_x;
            }
        }
        for (let index: number = start, stop: number = this.items.length; index < stop; index++) {
            let item: ScrollItem = this.items[index];
            if (this.dir == ScrollDirection.Vertical) {
                item.x = 0;
                item.y = start_pos;
                start_pos -= item.height + this.gap_y;
            }
            else {
                item.y = 0;
                item.x = start_pos;
                start_pos += item.width + this.gap_x;
            }
        }
    }

    private resize_content() {
        if (this.items.length <= 0) {
            this.content.width = 0;
            this.content.height = 0;
            return;
        }
        let last_item: ScrollItem = this.items[this.items.length - 1];
        if (this.dir == ScrollDirection.Vertical) {
            this.content.height = Math.max(this.height, last_item.height - last_item.y);
        }
        else {
            this.content.width = Math.max(this.width, last_item.x + last_item.width);
        }
    }

    set_data(datas: ScrollItemData[]) {
        this.clear_items();
        this.items = [];
        datas.forEach((data, index) => {
            let item: ScrollItem = this.pack_item(index, data);
            this.items.push(item);
        });
        this.update_ItemIndex();
        this.layout_items(0);
        this.resize_content();
        this.start_index = -1;
        this.stop_index = -1;
        this.curPos = -1;
        if (this.dir == ScrollDirection.Vertical) {
            this.content.y = 0;
        }
        else {
            this.content.x = 0;
        }
    }

    insert_data(index: number, ...datas: ScrollItemData[]) {
        if (datas.length == 0) {
            cc.info("nothing to insert");
            return;
        }
        if (!this.items) {
            this.items = [];
        }
        if (index < 0 || index > this.items.length) {
            cc.warn("invalid index", index);
            return;
        }
        let is_append: boolean = index == this.items.length;
        let items: ScrollItem[] = [];
        datas.forEach((data, index) => {
            let item: ScrollItem = this.pack_item(index, data);
            items.push(item);
        });
        this.items.splice(index, 0, ...items);
        this.update_ItemIndex();
        this.layout_items(index);
        this.resize_content();
        this.start_index = -1;
        this.stop_index = -1;
        this.curPos = -1;
        if (this.auto_scrolling && is_append) {
            this.scroll_to_end();
        }
    }

    //删除数据
    remove_data(index: number) {
        if (!this.items) {
            return;
        }
        if (this.items.length == 0) {
            return;
        }
        if (index < 0 || index > this.items.length) {
            console.log("无效的index", index);
            return;
        }
        this.recycle_item(this.items[index]);
        this.items.splice(index, 1);
        this.update_ItemIndex();
        this.layout_items(index);
        this.resize_content();
        this.start_index = -1;
        this.stop_index = -1;
        this.curPos = -1;
    }

    //更新索引
    update_ItemIndex() {
        this.items.forEach((_item, index) => {
            _item.data.index = index;
        });
    }

    append_data(...datas: ScrollItemData[]) {
        if (!this.items) {
            this.items = [];
        }
        this.insert_data(this.items.length, ...datas);
    }

    scroll_to_end() {
        if (this.dir == ScrollDirection.Vertical) {
            this.scrollview.scrollToBottom(2);
        }
        else {
            this.scrollview.scrollToRight(2);
        }
    }

    destroy_items() {
        this.clear_items();
        this.node_pools.forEach((pools, key) => {
            pools.forEach((node) => {
                node.destroy();
            });
        });
        this.node_pools = null;
        this.items = null;

        if (cc.isValid(this.scrollview.node)) {
            this.scrollview.node.off("scroll-to-bottom", this.on_scroll_to_end, this);
            this.scrollview.node.off("scroll-to-right", this.on_scroll_to_end, this);
        }
    }

    //获取对象
    get_Item(index) {
        if (index >= 0 && index < this.items.length) {
            return this.items[index];
        }
    }

    //刷新当前视界
    Refresh_viewPort() {
        let item: ScrollItem;
        var [start, stop] = this.getRange();
        for (let i = start; i <= stop + 1; i++) {
            if (!this.items[i]) {
                return
            }
            item = this.items[i];
            if (item.node) {
                this.item_setter.call(this.cb_host, item.node, item.data);
            }
        }
    }
}

